#include "StdAfx.h"

#include "Material.h"
#include "MaterialShader.h"
#include "VertexFactory.h"

#include "MeshMaterialShader.h"

using namespace RenderSystem;

/////////////////MaterialShaderType/////////////////////////


/**
 * Enqueues a compilation for a new shader of this type.
 * @param Material - The material to link the shader with.
 * @param MaterialShaderCode - The shader code for the material.
 */
void MaterialShaderType::BeginCompileShader(
	const Material* pMaterial,
	const TCHAR* pszMaterialShaderCode,
	const ShaderProfile& rShaderProfile
	)
{
	// Construct the shader environment.
	ShaderCompilerEnvironment Environment;
	Environment.IncludeFiles.insert(TEXT("Material.usf"),pszMaterialShaderCode);

	switch( pMaterial->GetBlendMode() )
	{
	case Material::BLEND_Opaque: Environment.Definitions.insert(TEXT("MATERIALBLENDING_SOLID"),TEXT("1")); break;
	case Material::BLEND_Masked: Environment.Definitions.insert(TEXT("MATERIALBLENDING_MASKED"),TEXT("1")); break;
	case Material::BLEND_Translucent: Environment.Definitions.insert(TEXT("MATERIALBLENDING_TRANSLUCENT"),TEXT("1")); break;
	case Material::BLEND_Additive: Environment.Definitions.insert(TEXT("MATERIALBLENDING_ADDITIVE"),TEXT("1")); break;
	case Material::BLEND_Modulate: Environment.Definitions.insert(TEXT("MATERIALBLENDING_MODULATE"),TEXT("1")); break;
	default: assert( false );
	}

	Environment.Definitions.insert(TEXT("MATERIAL_TWOSIDED"),pMaterial->IsTwoSided() ? TEXT("1") : TEXT("0"));
	//Environment.Definitions.insert(TEXT("MATERIAL_TWOSIDED_SEPARATE_PASS"),pMaterial->RenderTwoSidedSeparatePass() ? TEXT("1") : TEXT("0"));
	//Environment.Definitions.insert(TEXT("MATERIAL_LIT_TRANSLUCENCY_PREPASS"),pMaterial->RenderLitTranslucencyPrepass() ? TEXT("1") : TEXT("0"));
	//Environment.Definitions.insert(TEXT("MATERIAL_LIT_TRANSLUCENCY_DEPTH_POSTPASS"),pMaterial->RenderLitTranslucencyDepthPostpass() ? TEXT("1") : TEXT("0"));
	//Environment.Definitions.insert(TEXT("MATERIAL_CAST_LIT_TRANSLUCENCY_SHADOW_AS_MASKED"),pMaterial->CastLitTranslucencyShadowAsMasked() ? TEXT("1") : TEXT("0"));

	switch(pMaterial->GetLightingModel())
	{
	case Material::MLM_SHPRT: // For backward compatibility, treat the deprecated SHPRT lighting model as Phong.
	case Material::MLM_Phong: Environment.Definitions.insert(TEXT("MATERIAL_LIGHTINGMODEL_PHONG"),TEXT("1")); break;
	case Material::MLM_NonDirectional: Environment.Definitions.insert(TEXT("MATERIAL_LIGHTINGMODEL_NONDIRECTIONAL"),TEXT("1")); break;
	case Material::MLM_Unlit: Environment.Definitions.insert(TEXT("MATERIAL_LIGHTINGMODEL_UNLIT"),TEXT("1")); break;
	case Material::MLM_Custom: Environment.Definitions.insert(TEXT("MATERIAL_LIGHTINGMODEL_CUSTOM"),TEXT("1")); break;
	case Material::MLM_Anisotropic: Environment.Definitions.insert(TEXT("MATERIAL_LIGHTINGMODEL_ANISOTROPIC"),TEXT("1")); break;
	default: assert( false );
	};

	// Disable specular if the material is flagged as such...
	if (pMaterial->IsSpecularAllowed() == FALSE)
	{
		Environment.Definitions.insert(TEXT("DISABLE_LIGHTMAP_SPECULAR"), TEXT("1"));
		Environment.Definitions.insert(TEXT("DISABLE_DYNAMIC_SPECULAR"), TEXT("1"));
	}

	//if( pMaterial->GetTransformsUsed() & UsedCoord_World 
	//	|| Material->GetTransformsUsed() & UsedCoord_View
	//	|| Material->GetTransformsUsed() & UsedCoord_Local )
	//{
	//	// only use WORLD_COORDS code if a Transform expression was used by the material
	//	Environment.Definitions.Set(TEXT("WORLD_COORDS"),TEXT("1"));
	//}

	//if( pMaterial->GetTransformsUsed() & UsedCoord_WorldPos )
	//{
	//	Environment.Definitions.Set(TEXT("WORLD_POS"),TEXT("1"));
	//}

	Environment.Definitions.insert(TEXT("WORLD_POS"),TEXT("1"));

	// decals always need WORLD_COORD usage in order to pass 2x2 matrix for normal transform
	// using the color interpolators used by WORLD_COORDS
	//if( Material->IsUsedWithDecals() || Material->IsSpecialEngineMaterial() )
	//{
	//	Environment.Definitions.Set(TEXT("WORLD_COORDS"),TEXT("1"));
	//}
	//if( Material->IsUsedWithDecals() )
	//{
	//	Environment.Definitions.Set(TEXT("MATERIAL_DECAL"),TEXT("1"));
	//}

	if( pMaterial->UsesOneLayerDistortion() )
	{
		Environment.Definitions.insert(TEXT("MATERIAL_ONELAYERDISTORTION"),TEXT("1"));
	}

	if( pMaterial->IsUsedWithGammaCorrection() )
	{
		// only use USE_GAMMA_CORRECTION code when enabled
		Environment.Definitions.insert(TEXT("MATERIAL_USE_GAMMA_CORRECTION"),TEXT("1"));
	}

	//if( pMaterial->HasNormalmapConnected() )
	//{
	//	// This is used by the Anisotropic shader to determine whether to perform Graham-Schmidt Orthonormalization
	//	Environment.Definitions.Set(TEXT("MATERIAL_DEFINED_NORMALMAP"),TEXT("1"));
	//}

	//calculate the CRC for this type and store it in the shader cache.
	//this will be used to determine when shader files have changed.
	//DWORD CurrentCRC = GetSourceCRC();
	//UShaderCache::SetShaderTypeCRC(this, CurrentCRC, Platform);

	//warnf(NAME_DevShadersDetailed, TEXT("		%s"), GetName());

	//update material shader stats
	//UpdateMaterialShaderCompilingStats(Material);

	// Enqueue the compilation
	ShaderType::BeginCompileShader(NULL, rShaderProfile, Environment);
}

Shader* MaterialShaderType::FinishCompileShader( const Material* pMaterial, const ShaderCompilerOutput& rOutput )
{
	return (*m_ConstructCompiledType)( CompiledShaderInitializerType(this, rOutput, pMaterial) );
}


/////////////////MaterialShaderMap/////////////////////////

bool MaterialShaderMap::Compile(const Material* pMaterial, const TCHAR* pszMaterialShaderCode)
{
	ShaderCompileJobs& rShaderCompileJobs = ShaderCompilerManager::getSingleton().GetCurrentJob();
	if( !rShaderCompileJobs.empty() )
	{
		assert( false );
		return false;
	}

	// Iterate over all vertex factory types.
	VertexFactoryType::VertexFactoryTypes& rVertexFactoryTypes = VertexFactoryType::GetTypeList();

	bool bCompileSuccess = false;

	for( VertexFactoryType::VertexFactoryTypesIterator itrVertexFactory = rVertexFactoryTypes.begin();
		itrVertexFactory != rVertexFactoryTypes.end();
		++itrVertexFactory )
	{
		if( !(*itrVertexFactory)->IsUsedWithMaterials() )
			continue;

		MeshMaterialShaderMap* pMeshMaterialShaderMap = NULL;

		for( MeshMaterialShaderMapsIterator itrMeshMaterialShaderMap = m_MeshShaderMaps.begin();
			itrMeshMaterialShaderMap != m_MeshShaderMaps.end();
			++itrMeshMaterialShaderMap )
		{
			VertexFactoryType* pVertexFactoryType = (*itrMeshMaterialShaderMap)->GetVertexFactory();
			if( pVertexFactoryType == *itrVertexFactory )
				continue;
			pMeshMaterialShaderMap = *itrMeshMaterialShaderMap;
			break;
		}
		
		if( pMeshMaterialShaderMap == NULL )
			pMeshMaterialShaderMap = new MeshMaterialShaderMap();

		pMeshMaterialShaderMap->BeginCompile( pMaterial, pszMaterialShaderCode, *itrVertexFactory, m_objProfile );
	}

	// Iterate over all material shader types.
	ShaderType::ShaderTypes& rShaderTypes = ShaderType::GetTypeSets();
	for( ShaderType::ShaderTypesIterator itrShaderType = rShaderTypes.begin(); 
		itrShaderType != rShaderTypes.end(); ++itrShaderType )
	{
		MaterialShaderType* pMaterialShaderType = (*itrShaderType)->GetMaterialShaderType();
		if( pMaterialShaderType == NULL )
			continue;
		
		//if( !pMaterialShaderType->ShouldCache(InPlatform,Material) || Material->ShouldCache(InPlatform, ShaderType, NULL)'
			//continue;

		// Only compile the shader if we don't already have it
		if (!HasShader(pMaterialShaderType))
		{
			pMaterialShaderType->BeginCompileShader(
				pMaterial,
				pszMaterialShaderCode,
				m_objProfile
				);
		}
	}

	if( !bCompileSuccess )
		return false;

	//wait for compilation..
	//will be refactory later.
	bool bIsCompilationDone = false;
	while( !bIsCompilationDone )
	{
		ShaderCompileJobsIterator itrShaderCompileJob = rShaderCompileJobs.begin();
		for( ; itrShaderCompileJob != rShaderCompileJobs.end(); ++itrShaderCompileJob )
		{
			if( !(*itrShaderCompileJob).bSucceeded )
			{
				bIsCompilationDone = false;
				break;
			}
			bIsCompilationDone = true;
		}
	}

	for( VertexFactoryType::VertexFactoryTypesIterator itrVertexFactory = rVertexFactoryTypes.begin();
		itrVertexFactory != rVertexFactoryTypes.end();
		++itrVertexFactory )
	{
		if( !(*itrVertexFactory)->IsUsedWithMaterials() )
			continue;

		MeshMaterialShaderMap* pMeshMaterialShaderMap = NULL;

		for( MeshMaterialShaderMapsIterator itrMeshMaterialShaderMap = m_MeshShaderMaps.begin();
			itrMeshMaterialShaderMap != m_MeshShaderMaps.end();
			++itrMeshMaterialShaderMap )
		{
			VertexFactoryType* pVertexFactoryType = (*itrMeshMaterialShaderMap)->GetVertexFactory();
			if( pVertexFactoryType == *itrVertexFactory )
				continue;
			pMeshMaterialShaderMap = *itrMeshMaterialShaderMap;
			break;
		}
		
		assert( pMeshMaterialShaderMap != NULL );

		pMeshMaterialShaderMap->FinishCompile( pMaterial, rShaderCompileJobs );
	}

	for( UINT iJobIdx = 0; iJobIdx != rShaderCompileJobs.size(); ++iJobIdx )
	{
		ShaderCompileJob& rCompileJob = rShaderCompileJobs[iJobIdx];
		// Add MaterialShaderType results to this shader map.
		ShaderType::ShaderTypes& rShaderTypes = ShaderType::GetTypeSets();
		for( ShaderType::ShaderTypesIterator itrShaderType = rShaderTypes.begin(); 
			itrShaderType != rShaderTypes.end(); ++itrShaderType )
		{
			MaterialShaderType* pMaterialShaderType = (*itrShaderType)->GetMaterialShaderType();
			if( pMaterialShaderType == NULL )
				continue;

			Shader* pShader = pMaterialShaderType->FinishCompileShader( pMaterial, rCompileJob.objOutput );
			pMaterialShaderType->RegisterShader( pShader );
		}
	}

	// Reinitialize the vertex factory map.
	_InitVertexFactoryMap();

	// Register this shader map in the global map with the material's ID.
	g_sMaterialShaderMap.push_back( this );

	return bCompileSuccess;
}

void MaterialShaderMap::_InitVertexFactoryMap()
{

}

